<?php


namespace create_order_number;


class SnowFlake
{
    const START_STAMP = 1640966400000;  // "2022-01-01"
    const SEQUENCE_BIT = 12; // 序列号占用的位数
    const MACHINE_BIT = 5;  // 机器标识占用的位数
    const DATACENTER_BIT = 5;  // 数据中心占用的位数

    const MAX_DATACENTER_NUM = -1 ^ (-1 << self::DATACENTER_BIT); // 0x8000000000000001
    const MAX_MACHINE_NUM = -1 ^ (-1 << self::MACHINE_BIT);
    const MAX_SEQUENCE = -1 ^ (-1 << self::SEQUENCE_BIT);

    const MACHINE_LEFT = self::SEQUENCE_BIT;
    const DATACENTER_LEFT = self::SEQUENCE_BIT + self::MACHINE_BIT;
    const TIMESTAMP_LEFT = self::DATACENTER_LEFT + self::DATACENTER_BIT;

    /** @var int */
    private $datacenterId;
    /** @var int */
    private $machineId;
    /** @var int */
    private $sequence = 0;
    /** @var int */
    private $lastStamp = -1;

    public function __construct(int $dcId, int $mId) {
        if ($dcId < 0 || self::MAX_DATACENTER_NUM < $dcId) {
            throw new \InvalidArgumentException(
                "datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if ($mId < 0 || self::MAX_MACHINE_NUM < $mId) {
            throw new \InvalidArgumentException(
                "machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        $this->datacenterId = $dcId;
        $this->machineId = $mId;
    }

    public function nextId() {
        $currStamp = self::timeGen();
        if ($currStamp < $this->lastStamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }
        if ($currStamp - $this->lastStamp == 0) {
            $this->sequence = ($this->sequence + 1) & self::MAX_SEQUENCE;
            if ($this->sequence == 0) {
                $currStamp = $this->getNextMill();
            }
        } else {
            $this->sequence = 0;
        }
        $this->lastStamp = $currStamp;
        return ($currStamp - self::START_STAMP) << self::TIMESTAMP_LEFT //时间戳部分
            | $this->datacenterId << self::DATACENTER_LEFT       //数据中心部分
            | $this->machineId << self::MACHINE_LEFT             //机器标识部分
            | $this->sequence;                             //序列号部分
    }

    private function getNextMill() {
        $mill = self::timeGen();
        while ($mill - $this->lastStamp <= 0) {
            $mill = self::timeGen();
        }
        return $mill;
    }

    private static function timeGen() {
        list($ms, $sec) = explode(' ', microtime());
        return round((floatval($ms) + floatval($sec)) * 1000);
    }
}